/*
 * IOController.cpp
 *
 *  Created on: 09/02/2014
 *      Author: Luiggi
 */

#include "IOController.h"

#include "ExampleMaps.h"

#include "ScreenManager.h"
#include "triangulation.hpp"
#include "navimesh.hpp"

#include <ctime>
#include <cstdlib>
#include <cstdio>

IOController::IOController():
	screenManager(nullptr),
	triangulation(nullptr),
	mesh(nullptr),
	renderTriangles(true),
	renderConstraints(true),
	onlyConstrained(false),
	isRunning(true),
	isMovingCamera(false),
	isAutoInsertConstraint(false),
	constraintStep(0),
	constraintEnd(0),
	constraintList(nullptr)
{

}

void IOController::Initialize()
{
	// TODO: Get random seed
	srand(1);

	// Get screen manager instance
	screenManager = screenManager->GetInstance();

	TriangulationSetUp(TRIANGULATION_W, TRIANGULATION_H);
}

IOController::~IOController()
{
	// Destroy ScreenManager
	delete(screenManager);
}

void IOController::Run()
{
	// wait for events
	while(isRunning)
	{
		// handle the events in the queue
		HandleEvents();

		if(isAutoInsertConstraint)
		{
			if(constraintList != nullptr)
			{
				if(constraintStep < constraintEnd)
					InsertConstraint(constraintList[constraintStep++]);
			}
			else // if(constraintList == nullptr) /// Insert a random canstraint
			{
				insertRandomConstraint(TRIANGULATION_W, TRIANGULATION_H);
			}
		}

		if(renderTriangles)
			screenManager->RenderTriangulation();
		if(renderConstraints)
			screenManager->RenderConstraints(onlyConstrained);

		screenManager->Present();
	}
}

/// method to handle all events
void IOController::HandleEvents()
{
	// used to collect events
	SDL_Event event;

	while(SDL_PollEvent(&event))
	{
		switch(event.type)
		{
			case SDL_KEYDOWN:
				// handle key presses
				HandleKeyDown(event);
			break;
			case SDL_QUIT:
				isRunning = false;
			break;
			case SDL_MOUSEWHEEL:
			{
				if(event.wheel.y < 0)
				{
					screenManager->UpdateZoom(1.03f);
				}
				else //if(event.wheel.y >= 0)
				{
					screenManager->UpdateZoom(0.98f);
				}
			}
			break;
			case SDL_MOUSEBUTTONDOWN:
				if(event.button.button == SDL_BUTTON_LEFT)
					isMovingCamera = true;
			break;
			case SDL_MOUSEBUTTONUP:
				if(event.button.button == SDL_BUTTON_LEFT)
					isMovingCamera = false;
			break;
			case SDL_MOUSEMOTION:
			{
				if(!isMovingCamera)
					continue;

				screenManager->cameraY += event.motion.yrel * -(0.1f/(screenManager->zoom));
				screenManager->cameraX += event.motion.xrel *  (0.1f/(screenManager->zoom));
			}
			break;
			case SDL_WINDOWEVENT:
			{
				if(event.window.event == SDL_WINDOWEVENT_RESIZED)
				{
					screenManager->resizeEvent(event.window.data1,event.window.data2);
				}
			}
			break;
			default:
			break;
		}
	}
}

/// method to handle key press events
void IOController::HandleKeyDown(SDL_Event& keyDownEvent)
{
	switch(keyDownEvent.key.keysym.scancode)
	{
		case SDL_SCANCODE_ESCAPE:
			isRunning = false;
		break;
		case SDL_SCANCODE_SPACE:
			isAutoInsertConstraint = !isAutoInsertConstraint;
		break;
		case SDL_SCANCODE_UP:
			screenManager->cameraY -= 1000.0f;
		break;
		case SDL_SCANCODE_DOWN:
			screenManager->cameraY += 1000.0f;
		break;
		case SDL_SCANCODE_LEFT:
			screenManager->cameraX += 1000.0f;
		break;
		case SDL_SCANCODE_RIGHT:
			screenManager->cameraX -= 1000.0f;
		break;
		case SDL_SCANCODE_PAGEUP:
			//scr_manager->zoom -= 0.5f;
			screenManager->UpdateZoom(0.90f);
		break;
		case SDL_SCANCODE_PAGEDOWN:
			//scr_manager->zoom += 0.5f;
			screenManager->UpdateZoom(1.11f);
		break;
		case SDL_SCANCODE_F:
		case SDL_SCANCODE_1:
		{
			static bool wireFrame = false;
			if (wireFrame)
				glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
			else
				glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
			wireFrame = ! wireFrame;
		}
		break;
		case SDL_SCANCODE_B:
		case SDL_SCANCODE_2:
		{
			static bool blendOn = false;
			if (blendOn)
			{
			    glDisable(GL_BLEND);
			}
			else
			{
				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
				glEnable(GL_BLEND);
			}
			blendOn = ! blendOn;
		}
		break;
		case SDL_SCANCODE_C:
		case SDL_SCANCODE_3:
			renderConstraints = !renderConstraints;
		break;
		case SDL_SCANCODE_O:
		case SDL_SCANCODE_4:
			onlyConstrained = !onlyConstrained;
		break;
		case SDL_SCANCODE_T:
		case SDL_SCANCODE_5:
			renderTriangles = !renderTriangles;
		break;
		case SDL_SCANCODE_A:
		case SDL_SCANCODE_6:
			/// TODO Remove last constraint
		break;
		case SDL_SCANCODE_D: /// Insert Constraint
		case SDL_SCANCODE_7:
		{
			if(constraintList != nullptr)
			{
				if(constraintStep < constraintEnd)
					InsertConstraint(constraintList[constraintStep++]);
			}
			else // if(constraintList == nullptr) /// Insert a random canstraint
			{
				insertRandomConstraint(TRIANGULATION_W, TRIANGULATION_H);
			}
		}
		break;
		case SDL_SCANCODE_EQUALS:
		{
			fprintf(stdout, "BREAKPOINT HIT! ('=' pressed)");
		}
		break;
		case SDL_SCANCODE_PRINTSCREEN:
			screenManager->saveScreenShot();
		break;
		case SDL_SCANCODE_F1: /// SWMap1
			TriangulationSetUp(TRIANGULATION_W, TRIANGULATION_H);
			isAutoInsertConstraint = false;
			constraintStep = 0;
			constraintEnd  = ExampleMaps::SWMap1Size;
			constraintList = ExampleMaps::SWMap1;
		break;
		case SDL_SCANCODE_F2: /// Gear2DMap
			TriangulationSetUp(TRIANGULATION_W, TRIANGULATION_H);
			isAutoInsertConstraint = false;
			constraintStep = 0;
			constraintEnd  = ExampleMaps::Gear2DSize;
			constraintList = ExampleMaps::Gear2D;
		break;
		case SDL_SCANCODE_F3: /// SWMap2
			TriangulationSetUp(TRIANGULATION_W, TRIANGULATION_H);
			isAutoInsertConstraint = false;
			constraintStep = 0;
			constraintEnd  = ExampleMaps::SWMap2Size;
			constraintList = ExampleMaps::SWMap2;
		break;
		case SDL_SCANCODE_F4: /// Random
			TriangulationSetUp(TRIANGULATION_W, TRIANGULATION_H);
			isAutoInsertConstraint = false;
			constraintStep = 0;
			constraintEnd  = 0;
			constraintList = nullptr;
		break;
		default:
		break;
	}
	return;
}

void IOController::InsertConstraint(Constraint constraint)
{
	/// TODO Should this method receive a constraint? V1 and V2 are not set in this constraint...
	triangulation->insertConstraint(constraint);
}

void IOController::insertRandomConstraint(float w, float h)
{
	static int id = 5;
	triangulation->insertConstraint(rand()%(int)(w-1), rand()%(int)(h-1),
			rand()%(int)(w-1), rand()%(int)(h-1), id++);
}

void IOController::generateNaviMesh()
{
	mesh = new Navimesh(triangulation);
	mesh->RebuildMesh();
}

void IOController::TriangulationSetUp(float w, float h)
{
	if(triangulation)
	{
		delete triangulation;
	}

	triangulation = new Triangulation(w, h);

	screenManager->triangulation = triangulation;
	triangulation->RegisterRenderCallBack(screenManager, ScreenManagerCallBack);

	// Borda
	triangulation->insertConstraint(0, 0, w, 0, 1);
	triangulation->insertConstraint(w, 0, w, h, 2);
	triangulation->insertConstraint(w, h, 0, h, 3);
	triangulation->insertConstraint(0, h, 0, 0, 4);

	screenManager->renderTriangulation = true;
}
